﻿/* Класс выполняет основные действия программы - объединение файлов
 */
using System;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using FileIOLibrary;

namespace CSFontan
{

    class CSFileClass
    {
        // конструктор класса
        public CSFileClass()
        {
            this.MonthData = new Dictionary<KeyValuePair<int, int>, CSValue>();
            this.AverSeansPerDay = 0;
            this.dataFolder = new FolderBrowserDialog();
            this.fd = new FileClass();
        }

        // структура для хранения данных из объединяемых файлов
        // применяется в Dictionary MonthData
        public struct CSValue
        {
            // конструктор структуры
            public CSValue(string MJDFull, int MJDInt, double DfCSCLK, double DfBIAS, string FileName)
            {
                //this.MJD = MJD;
                this.MJDFull = MJDFull;
                this.MJDInt = MJDInt;
                this.DfCSCLK = DfCSCLK;
                this.DfBIAS = DfBIAS;
                this.FileName = FileName;
            }


//            public double MJD;    // MJD в формате MJD.<доля_суток>
            public string MJDFull;  // MJD в формате MJD.<доля_суток>
            public int MJDInt;      // MJD без доли суток
            public double DfCSCLK;  // дрейф CS относительно i-х часов
            public double DfBIAS;   // поправка к дрейфу
            public string FileName; // имя файла данных фонтана
        }   // end decribe of structure

        // структура локализации - локальных настроек
        public struct MyRegionalSettings
        {
            public CultureInfo culture;
            public NumberStyles styles;
        }

        // Поля класса
        protected FileInfo[] CSDatas;   // массив файлов данных фонтана
        public Dictionary<KeyValuePair<int, int>, CSValue> MonthData; // массив структуированных данных измерений
        public string KeyForSearch;     // ключ для поиска в массиве MonthData
        public int MJDForSearch;        // часть ключа MJD для поиска
        public int PropOfDay;           // часть ключа доля суток для поиска
        public FolderBrowserDialog dataFolder;  // каталог размещения данных
        protected decimal AverSeansPerDay;      // среднее кол-во сеансов в день
        private string OutFileName; // имя выходного объединенного файла
        protected Int32 DayCount;   // количество дней в расчитываемом периоде, за к-е есть измерительные данные
        private FileClass fd;     // ссылка для работы с библиотекой FileIOLibrary

        // Свойства класса
        public FileClass FD
        {
            get { return fd; }
        }

        // Процедура выполняет проверку:
        // 1) id CS (номера цезия) из имени файла и из заголовка файла (должны совпадать)
        // 2) соответствие имени файла шаблону имени (маске) "CSXXX_", где ХХХ - цифры
        // 
        // Входные данные:
        // FileName - имя проверяемого файла
        public bool TestIdCS(string FileName)
        {
            Int16 IdCSFromFileName = 0;
            Int16 IdCSFromFileHeader = 0;
            String ErrorMessage = String.Empty;
            FileClass.ReadData Str = new FileClass.ReadData(false);

            // проверка на соответствие имени файла данных
            if (Regex.IsMatch(FileName, @"CS\d{3}_"))
                Int16.TryParse(FileName.Substring(FileName.IndexOf("CS") + 2, 3), out IdCSFromFileName);
            // имя файла не соответствует установленному шаблону
            else
            {
                ErrorMessage = String.Format("Имя файла {0} не соответствует маске", FileName);
                fd.WriteToLog(ErrorMessage);
                MessageBox.Show(ErrorMessage);
                return false;
            }                
            while (!fd.FileReader.EndOfStream)
            {
                Str = fd.LineRead();
                // поиск строки с номером цезия
                if (Regex.IsMatch(Str.Line_i, @"CS \d{3} \("))
                {
                    Int16.TryParse(Str.Line_i.Substring(Str.Line_i.IndexOf("10"), 3), out IdCSFromFileHeader);
                    break;
                }
            }
            // номер цезия из имени файла и заголовка не совпали
            if (IdCSFromFileName != IdCSFromFileHeader)
            {
                ErrorMessage = String.Format("В файле {0} не совпали номера цезия из имени {1} и заголовка файла {2}", FileName, IdCSFromFileName, IdCSFromFileHeader);
                fd.WriteToLog(ErrorMessage);
                MessageBox.Show(ErrorMessage);
                return false;
            }
            else
            {
                return true;
            } 
        }

        // Процедура проверяет совпадение номера опорного генератора
        // из заголовка файла и заданного в форме
        private bool TestIDRefNum(string FileName, Int16 RefNum)
        {
            Int16 IdMazNum = 0;
            FileClass.ReadData Str = new FileClass.ReadData(false);
            String ErrorMessage = String.Empty;

            if (FileName == String.Empty)
                return false;

            while (!fd.FileReader.EndOfStream)
            {
                Str = fd.LineRead();
                // поиск строки с номером мазера
                if (Regex.IsMatch(Str.Line_i, @"^CLOCK\s+14038"))
                {
                    Int16.TryParse(Str.Line_i.Substring(Str.Line_i.IndexOf("14038")+5, 2), out IdMazNum);
                    break;
                }
            }
            if (RefNum != IdMazNum)
            {
                ErrorMessage = String.Format("Номер опорного генератора в имени файла {0} и заголовке файла {1} не совпадают", FileName, IdMazNum);
                MessageBox.Show(ErrorMessage);
                fd.WriteToLog(ErrorMessage);
                return false;
            }                
            else
                return true;
        }

        // Процедура проверяет правильность формата таблицы измерений:
        // 1) количество колонок
        // 2) формат первой колонки (MJD) - XXXXX.XXXX, где Х цифра
        //
        // Входные данные:
        // LineFromFile - строка с измерениями из входного файла
        private bool TestColNum(string LineFromFile)
        {
            Regex pattern = new Regex(@"\s+");
            string[] mass = new string[256];
            mass = pattern.Split(LineFromFile.Trim());
            if ((mass.Length == 3) && (Regex.IsMatch(mass[0], @"^\d{5}.\d{4}")))
               return true;
            else
               return false;

        }

        // Процедура устанавливает региональные установки,
        // соответствующие "en-US" и разделитель чисел "."
        //
        public MyRegionalSettings SetRegionCulture()
        {
            MyRegionalSettings MySetting = new MyRegionalSettings();

            NumberFormatInfo MySet = new NumberFormatInfo();
            MySet.NumberDecimalSeparator = @".";
            MySetting.culture = new CultureInfo("en-US");

            MySetting.styles = NumberStyles.AllowDecimalPoint | NumberStyles.Number;
            return MySetting;
        }

        // Процедура проверяет правильность преобразования
        // даты MJD из файла измерений фонтана
        // (первая колонка таблицы измерений)
        //
        // Входящие данные:
        // LineFromFile - анализируемая строка файла
        // MJD - юлианская дата из LineFromFile, возвращается для тестирования преобразования (ref)
        // mass - массив, в который пословно разбивается строка LineFromFile, возвращается для анализа в тестах
        // MySettings - региональные установки "en-US"
        //
        public bool TestMJDFromFile(string LineFromFile, ref double MJD, ref string[] mass, MyRegionalSettings MySettings)
        {
            Regex pattern = new Regex(@"\s+");
            mass = pattern.Split(LineFromFile.Trim());
            // проверка первого столбца - MJD
            MJD = 0;
            if (Double.TryParse(mass[0], MySettings.styles, MySettings.culture, out MJD))
                return true;
            else
                return false;
        }

        // Процедура проверяет правильность формата измерительных данных 
        // в столбцах:
        // 1) MJD - должен соответствовать шаблону XXXXX.XXXX и попадать в период между MJDb и MJDe
        // 2) DfCSClk - должно быть числом с плавающей запятой
        // 3) DfBIAS - должно быть числом с плавающей запятой
        //
        // Входные данные:
        // LineFromFile - строка измерений из файла
        // FileName - имя входного файла с измерениями
        // MJDb - дата начала периода
        // MJDe - дата окончания периода
        public bool TestColFormat(string LineFromFile, string FileName, Int32 MJDb, Int32 MJDe)
        {
            double MJD = 0;
            string[] mass = null;
            string[] KeysMJD = null;  // массив из 2-х элементов KeysMJD[0]=MJD, KeysMJD[1]=доля_суток

            // установка требуемых региональных установок
            MyRegionalSettings MySettings = SetRegionCulture();

            // проверка первого столбца - MJD
            if (!TestMJDFromFile(LineFromFile, ref MJD, ref mass, MySettings))
                return false;
            // проверка непопадания в интервал между MJDb и MJDe
            if ((MJD < MJDb) || (MJD > MJDe + 1))
                return false;
            // проверка соответствия DfCSlk числовому формату с плавающей точкой
            double DfCSClk = 0;
            if (!double.TryParse(mass[1], NumberStyles.Float, MySettings.culture, out DfCSClk))
                return false;
            // проверка соответствия DfBIAS числовому формату с плавающей запятой
            double DfBIAS = 0;
            if (!double.TryParse(mass[2], NumberStyles.Float, MySettings.culture, out DfBIAS))
                return false;
            // проверка данных на наличие в массиве
            // mass[0] - MJD.<доля_суток>
            KeysMJD = mass[0].Split('.');
            if (! int.TryParse(KeysMJD[0], NumberStyles.Integer, MySettings.culture, out MJDForSearch))
                return false;
            if (! int.TryParse(KeysMJD[1], NumberStyles.Integer, MySettings.culture, out PropOfDay))
                return false;
            // найдено совпадение - данные за эту же дату
            KeyValuePair<int, int> sea = new KeyValuePair<int, int>(MJDForSearch, PropOfDay);
            var query = (from item in MonthData
                         where item.Key.Equals(sea)
                         select item).Count();
            if (query > 0)
               return false;
            else
            {
                MonthData.Add(new KeyValuePair<int, int>(MJDForSearch, PropOfDay), new CSValue(mass[0], (int)MJD, DfCSClk, DfBIAS, FileName));
                return true;
            }
        }

        // Процедура выполняет проверку попадания данных измерений
        // файла в анализируемый интервал
        //
        // Входные данные:
        // FileName - имя анализируемого файла с измерительными данными фонтана
        // MJDb, MJDe - анализируемый интервал (дата начала, дата окончания)
        public bool TestFileDatas(string FileName, Int32 TestMJDb, Int32 TestMJDe)
        {
            Int32 MJDbFromFile = 0;           // первая дата из таблицы данных измерений
            Int32 MJDeFromFile = 0;           // последняя дата из таблицы данных измерений
            decimal MJDIntermedia = 0;        // промежуточное значение MJD текущей строки в формате decimal
            // первоначальная проверка входных данных
            if ((FileName == String.Empty) || (TestMJDe == 0) || (TestMJDe == 0))
                return false;
            // чтение файла измерений в List целиком
            List<string> TextBufer = null;
            if (!File.Exists(FileName))
                return false;
            using (StreamReader fs = new StreamReader(FileName))
            {
                TextBufer = new List<string>(fs.ReadToEnd().Split(new char[] { '\n' }));
                TextBufer.RemoveAt(TextBufer.Count - 1);
                fs.Close();
            }
            // установка требуемых региональных данных
            MyRegionalSettings USASettings = SetRegionCulture();
            // поиск первой строки измерительных данных
            foreach (string LineI in TextBufer)
            {
                if (TestColNum(LineI))
                {
                    if ((decimal.TryParse(Regex.Split(LineI.Trim(), @"\s+")[0],
                        USASettings.styles, USASettings.culture, out MJDIntermedia)) && (MJDbFromFile == 0))
                        // выделение целой части MJD без доли суток
                        MJDbFromFile = (int)Math.Truncate(MJDIntermedia);
                    // поиск последней даты MJD в файле
                    else if (decimal.TryParse(Regex.Split(LineI.Trim(), @"\s+")[0],
                            USASettings.styles, USASettings.culture, out MJDIntermedia))
                    {
                        if (MJDIntermedia != 0)
                            MJDeFromFile = (int)Math.Truncate(MJDIntermedia);
                    }
                }
            }
            // проверка того, что последнее и первое MJD из файла измерений найдены
            if ((MJDbFromFile != 0) && (MJDeFromFile != 0))
            {
                if (((MJDbFromFile >= TestMJDb) && (MJDbFromFile <= TestMJDe)) ||
                   ((TestMJDb <= MJDeFromFile) && (MJDeFromFile <= TestMJDe)))
                    return true;
                else
                    return false;
            }
            else
                return false;
        }

        // Процедура осуществляет выбор каталога чтения файлов
        // и записывает подходящие по маске файлы в массив CSDatas
        //
        // MJDb - начальная дата периода
        // MJDe - конечная дата периода
        // CSnum - номер цезия (103 или 105 в нынешней конфигурации)
        public bool PrepareFileList(Int32 MJDb, Int32 MJDe, Int16 CSNum)
        {
            Int32 MJDf;                      // MJD дата из имени файла
            UInt64 i = 0;                    // индекс текущей позиции в массиве имен файлов
            UInt64 j = 0;                    // индекс проверяемой позиции в массиве имен файлов

            // отображение диалогового окна выбора каталога
            if (dataFolder.ShowDialog() != DialogResult.OK)
                return false;
            
            // пустая строка с именем каталога размещения файлов
            if (dataFolder.SelectedPath == String.Empty)
                return false;
            // каталог с указанным именем не существует
            if (dataFolder.SelectedPath.Trim().Length <= 0)
                return false;
            DirectoryInfo CSDir = new DirectoryInfo(dataFolder.SelectedPath);
            if (!CSDir.Exists)
                return false;
            // в каталоге нет файлов с указанным шаблоном имени файла
            CSDatas = CSDir.GetFiles("CS" + CSNum.ToString() + @"*.txt");
            if (CSDatas.Length <= 0)
                return false;
            // выбор в списке подходящих по дате файлов
            // остальные имена удаляются из массива
            foreach (FileInfo fd in CSDatas)
            {
                if (Regex.IsMatch(fd.Name, @"^CS\d{3}_\d{5}.txt"))
                {
                    Int32.TryParse(fd.Name.Substring(fd.Name.IndexOf(@"_") + 1, 5), out MJDf);
                    // проверка попадания файла в период MJDb - MJDe
                    if (!TestFileDatas(fd.FullName, MJDb, MJDe))
                        j++;
                    else
                    {
                        CSDatas[i] = CSDatas[j];
                        j++;
                        i++;
                    }
                }
                else
                    j++;
            }
            // изменение размера массива - уменьшение - только файлы, входящие в диапозон 
            Array.Resize(ref CSDatas, (int)i);
            if (CSDatas.Length > 0)
                return true;
            // в каталоге не оказалось файлов за нужные даты
            else
                return false;
        }

        // Процедура выполняет слияние измерительных данных за 
        // указанный перниод
        // Входные данные:
        // MJDb - MJD дата начала интервала
        // MJDe - MJD дата окончания интервала
        // CSNum - номер цезиевого фонтана
        // RefNum - номер опорного генератора
        // DataPerPeriodN - % данных/период
        // CountDaysN - % количества дней
        // DataPerDayN - % данных/день
        public void FileUnion(Int32 MJDb, Int32 MJDe, Int16 CSNum, Int16 RefNum, 
            Int16 DataPerPeriodN, Int16 CountDaysN, Int16 DataPerDayN)
        {
            Int64 i = 0;                     // индекс буфера накопления данных
            Int32 j = 0;                     // номер читаемой из файла строки
            Int16 Priznak = 1;               // для записи в выходной файл 1- данные соответствуют
                                             // 3-м условиям, 0 - не соответствуют
            FileClass.ReadData Str = new FileClass.ReadData(false);
            String ErrorMessage = String.Empty;

            // для чтения из файла задается разделитель
            // независимо от региональных установок
            NumberFormatInfo MySet = new NumberFormatInfo();
            MySet.NumberDecimalSeparator = @".";
            CultureInfo culture = new CultureInfo("en-US");

            // выбор каталога размещения данных
            // чтение списка файлов
            if (PrepareFileList(MJDb, MJDe,CSNum))
            {
                foreach (FileInfo fd_ in CSDatas)
                {
                    if (fd.StreamOpen(dataFolder.SelectedPath+@"\"+fd_.Name, FileClass.FileReadingRegim))
                    {
                        if (!TestIdCS(fd_.Name))
                        {
                            fd.StreamClose(fd.readFileName, FileClass.FileReadingRegim);
                            continue;
                        }
                        if (!TestIDRefNum(fd_.Name, RefNum))
                        {
                            fd.StreamClose(fd.readFileName, FileClass.FileReadingRegim);
                            continue;
                        }
                        while (!fd.FileReader.EndOfStream)
                        {
                            Str = fd.LineRead();
                            // Произошла ошибка чтения, т.к. указатель в файле
                            // переместить назад невозможно, закрываем и выходим -
                            // нужно начинать обработку по-новой.
                            if (!Str.ReurnEx)
                            {
                                ErrorMessage = String.Format("Ошибка чтения файла {0}, строка {1}", fd.readFileName, j);
                                fd.WriteToLog(ErrorMessage);
                                MessageBox.Show(ErrorMessage);
                                fd.StreamClose(fd.readFileName, FileClass.FileReadingRegim);
                                continue;
                            }
                            j++;
                            // формальная проверка кол-ва столбцов в таблице измерений
                            // файла данных цезия
                            if (!TestColNum(Str.Line_i))
                                continue;
                            // формальная проверка данных измерений и
                            // запись в буфер накопления MonthData
                            if (TestColFormat(Str.Line_i, fd.readFileName, MJDb, MJDe))
                                i++;
                        }
                        fd.StreamClose(fd.readFileName, FileClass.FileReadingRegim);
                        // временная запись в файл для проверки алгоритма
                    }
                }
                // проверка на количество данных - 75% от исходных MJDb & MJDe с формы
                // проверка по наличию данных за период по датам
                if (!Test75Percent(MJDb, MJDe, DataPerPeriodN))
                {
                    Priznak = 0;
                    ErrorMessage = @"Проверка на количество данных 75% не пройдена - Test75Percent";
                    MessageBox.Show(ErrorMessage);
                    fd.WriteToLog(ErrorMessage);
                }
                else if (!TestPeriod20Days(MJDb, MJDe, CountDaysN))
                {
                    Priznak = 0;
                    ErrorMessage = @"Проверка на наличие данных по датам не пройдена - TestPeriod20Days";
                    MessageBox.Show(ErrorMessage);
                    fd.WriteToLog(ErrorMessage);
                }
                else
                {
                    if (!TestCountSeans(MJDb, DataPerDayN))
                    {
                        ErrorMessage = @"Тест на количество данных в начальную дату не пройден - TestCountSeans";
                        MessageBox.Show(ErrorMessage);
                        fd.WriteToLog(ErrorMessage);
                        Priznak = 0;
                    }
                    if (!TestCountSeans(MJDe, DataPerDayN))
                    {
                        ErrorMessage = @"Тест на количество данных в конечную дату не пройден - TestCountSeans";
                        MessageBox.Show(ErrorMessage);
                        fd.WriteToLog(ErrorMessage);
                        Priznak = 0;
                    }
                    else
                        Priznak = 1;
                }
                WriteToOutFile(MJDe, CSNum, Priznak);
                MessageBox.Show("Обработка закончена");
            }
        } // end procedure FileUnion

        // Процедура выполняет проверку массива MonthData на заполненность:
        // должно быть DataPerPeriodN% данных между MJD начальной и MJD конечной
        //
        // Выходные данные:
        // признак соответствия условию true - соответствует, false - не соответствует
        public bool Test75Percent(Int32 MJDbFromForm, Int32 MJDeFromForm, Int16 DataPerPeriodN)
        {
            Int32 MJDb = 0;    // переменная начала периода из MonthData
            Int32 MJDe = 0;    // переменная конца периода из MonthData
            Int16 CalcPercent = 0;  // расчитанный процент данных

            // неправильно заданы параметры процедуры
            if (MJDbFromForm > MJDeFromForm)
                return false;
            // даты не должны быть нулевыми
            if ((MJDbFromForm == 0) || (MJDeFromForm == 0))
                return false;
            if (MonthData.Count > 0)
            {
                // поиск последнего индекса в List MonthData (предполагается отсутствие пустых данных)
                int end = MonthData.Count - 1;
                MJDb = (int)MonthData.First().Value.MJDInt;
                MJDe = (Int32)MonthData.Last().Value.MJDInt;
                if ((MJDb <= 0) || (MJDe <= 0))
                    return false;
                // проверка на % данных
                CalcPercent = Convert.ToInt16((MJDe - MJDb + 1)/(MJDeFromForm - MJDbFromForm + 1)*100);
                fd.WriteToLog(String.Format("Расчитанный процент данных за период по разнице дат: {0}", CalcPercent));
                if ( CalcPercent >= DataPerPeriodN)
                    return true;
                else
                    return false;
            }
            else
                return false;
        }   // end Test75Percent

        // Процедура выполняет проверку продолжительности данных -
        // должно быть не менее 20 дней
        //
        // Входные данные: MJDbFromForm - дата начала рассчитываемого периода
        //                 MJDeFromForm - дата окончания рассчитываемого периода
        // Выходные данные: признак соответствия необходимым условиям - 
        //                  данных в периоде должно быть не менее 20 дней
        public bool TestPeriod20Days(Int32 MJDbFromForm, Int32 MJDeFromForm, Int16 CountDaysN)
        {
            Double DayCountForCalc = 0;
            DayCount = 0; // Счетчик количества дней в объединенном файле данных
            Int16 DayCountPercent = 0; // счетчик количества дней в процентах
            Int32 MJDPrev = 0; // Проверяемая дата MJD в MonthData без сеанса

            // формальная проверка входных данных - 
            // дата начала периода не должна быть больше даты конца периода
            if (MJDbFromForm > MJDeFromForm)
                return false;

            // формальная проверка входных данных -
            // дата конца периода - дата начала периода >= 20 дней
//            if (MJDeFromForm - MJDbFromForm < CountDaysN)
//                return false;

            // количество дней в периоде не должно быть 0 или меньше 0
            if (CountDaysN <= 0)
                return false;

            // подсчет количества дней
            foreach (var i in MonthData)
            {
                // обработка ситуации с неправильной датой
                if (i.Value.MJDInt <= 0)
                    continue;
                // начало отсчета или несовпадение MJD текущей и предыдущей
                if ((i.Value.MJDInt != 0) && ((int)i.Value.MJDInt != MJDPrev))
                {
                    DayCount++;
                    MJDPrev = (int)i.Value.MJDInt;
                }
            }
            // перевод DayCount в проценты
            DayCountForCalc = (Double) DayCount;
            DayCountPercent = (Int16)Math.Round((decimal)(DayCountForCalc/(MJDeFromForm - MJDbFromForm)*100));
            fd.WriteToLog(String.Format("Расчитанный процент данных за период по количеству дней: {0}", DayCountPercent));
            if (DayCountPercent < CountDaysN)
                return false;
            else
                return true;
        } // end TestPeriod20Days
         
        // Процедура выполняет подсчет среднего арифметического
        //
        // Входные данные:
        // CountDays - количество дней с данными в описываемом периоде
        public bool CountAverageNumber(Int32 CountDays)
        {
            // проверка длины массива измерений и входного параметра
            if ((MonthData.Count <= 0) || (CountDays <= 0))
                return false;

            decimal[] CountSeans = new decimal[CountDays];

            decimal CountI = 1;             // подсчет количества сеансов в i-м дне
            Int32 MJD_i = 0;                // MJD предыдущего дня
            Int32 i = 0;
            decimal SumSeans = 0;           // кол-во сеансов за все дни
            foreach (var csValue_i in MonthData)
            {
                // некорректные данные
                if (csValue_i.Value.MJDInt <= 0)
                    continue;

                // поменялась дата MJD или Dictionary достиг конца
                //
                if ((csValue_i.Value.MJDInt != MJD_i) |
                    (MonthData.Last().Key.Equals(csValue_i.Key)))
                {
                    if (MJD_i != 0)
                    {
                        CountSeans[i] = CountI;
                        SumSeans += CountI;
                        CountI = 1;
                        i++;
                    }
                    MJD_i = csValue_i.Value.MJDInt;
                }
                else if (csValue_i.Value.MJDInt == MJD_i)
                    CountI++;
            }
            AverSeansPerDay = Math.Round(SumSeans / CountDays);
            if (AverSeansPerDay == 0)
                return false;
            else
                return true;
            
        } // end procedure CountAverageNumber

        // Процедура выполняет проверку на соответствие количеству 
        // сеансов проверяемую дату MJD
        //
        // Входящие данные:
        // TestMJD - проверяемая дата
        // DataPerDaysN - требуемое количество сеансов в день в %
        public bool TestCountSeans(Int32 TestMJD, Int16 DataPerDaysN)
        {
            Decimal CalcDataPerDay = 0;

            // проверка входящих данных
            if ((TestMJD == 0) || (DataPerDaysN == 0))
                return false;

            // Перевод процентов в доли
            //decimal ProportionOfData = Decimal.Round((decimal)(DataPerDaysN / 100), 2);
            decimal ProportionOfData = Decimal.Round((decimal)DataPerDaysN / 100, 2);
            // не найдена дата в Dictionary (поиск происходит по целой MJD)
            var query = (from csValue in MonthData
                         where csValue.Key.Key.CompareTo(TestMJD) == 0
                         select csValue).Count();
            if (query == 0)
                return false;
            else
            {
                Int32 CountSeansTestMJD = 0;
                // кол-во сеансов в тестируемую MJD
                CountSeansTestMJD = query;           
                // проверка соответствия % данных
                if (DayCount == 0)
                    return false;
                if (!CountAverageNumber(DayCount))
                    return false;
                CalcDataPerDay = Decimal.Round((decimal) CountSeansTestMJD/AverSeansPerDay, 2);
                fd.WriteToLog(String.Format("Процент данных в 3-х днях от {0} - {1}", TestMJD, CalcDataPerDay*100));
                if (CalcDataPerDay >= ProportionOfData)
                    return true;
                else
                    return false;
            }
        } // end procedure TestCountSeans

        // Процедура выполняет проверку целостности данных по дням:
        // первый и последний день периода должны иметь заполненость 
        // данными 3/4 от номинального объема
        //
        // Входные данные:
        // MJDbFromForm - дата начала периода из формы
        // MJDeFromForm - дата окончания периода из формы
        // DataPerDaysN - процент целостности данных в день
        private bool TestDIPerDay(Int32 MJDbFromForm, Int32 MJDeFromForm, Int16 DataPerDaysN)
        {
            // сбор статистики по среднему количеству данных за день 
            // за рассчитываемый период
            if (! CountAverageNumber((Int32)DataPerDaysN))
                return false;
            // проверка соответствия критерию DataPerDaysN первого дня периода
            if (! TestCountSeans(MJDbFromForm, DataPerDaysN))
                return false;
            return true;
        }   // end procedure TestDIPerDay

        // процедура формирует выходной файл с именем cz<НомерФонтана>_<MJDe>.txt
        //
        // Входные данные:
        // MJDeFromForm - дата окончания периода из формы
        // CSNum - номер цезия (103 или 105 на данный момент)
        // Priznak - признак корректности данных (соответствие 3-м условиям)
        //           1 - соответствует, 0 - не соответствует
        public bool WriteToOutFile(Int32 MJDeFromForm, Int16 CSNum, Int16 Priznak)
        {
            if ((MJDeFromForm == 0) || (CSNum == 0))
                return false;
            this.OutFileName = String.Concat(dataFolder.SelectedPath.ToString(),@"\dfCZ", CSNum.ToString(), "_", MJDeFromForm.ToString(), ".txt");
            string LineToFile = String.Empty;
            // открытие файла на запись
            if (!fd.StreamOpen(OutFileName, FileClass.FileWritingRegim))
                return false;
            fd.LineWrite("Priznak " + Priznak.ToString());
            foreach (CSValue i in MonthData.Values)
            {
                LineToFile = String.Format("{0}{1,9}", i.MJDFull,
                                           (i.DfCSCLK - i.DfBIAS).ToString("F3",
                                                                           CultureInfo.CreateSpecificCulture("en-US")));
//                                           i.DfCSCLK.ToString("F3", CultureInfo.CreateSpecificCulture("en-US")));
                fd.LineWrite(LineToFile);
            }
            if (!fd.StreamClose(OutFileName, FileClass.FileWritingRegim))
                return false;
            return true;
        }

    }
}
